#!/usr/bin/python

from gevent import wsgi
import psycopg2
import psycopg2.extras
import json
import urlparse
import hashlib
import random
import os
import sys

def mkResponse(query_string, response_obj):
    cb = getCb(query_string)
    if cb:
        return getCb(query_string) + '(' + json.dumps(response_obj) + ');'
    else:
        return response_obj

def jsonRespond(start_response):
    start_response('200 OK', [('Content-Type', 'text/javascript')])

def getData(query_string):
    return urlparse.parse_qs(query_string)

def getCb(query_string):
    if 'callback' in query_string:
        return query_string['callback'][0]
    else:
        return False

def determine_path ():
    """Borrowed from wxglade.py"""
    try:
        root = __file__
        if os.path.islink(root):
            root = os.path.realpath(root)
        return os.path.dirname (os.path.abspath(root))
    except:
        print "I'm sorry, but something is wrong."
        print "There is no __file__ variable. Please contact the author."
        sys.exit()

def sendFile(filetype, filename, start_response):
    start_response('200 OK', [('Content-Type', filetype)])
    contents = open(determine_path() + '/frontend/' + filename).read()
    return contents

def sendError(query_string, error_message, start_response):
    start_response('200 OK', [('Content-Type', 'application/json')])
    return mkResponse(query_string, {'error': error_message})

def readConf():
    config = {}
    ourconf = open(determine_path() + '/stattrd.conf')
    for line in ourconf.read().split('\n'):
        if len(line.split('=')) > 1:
            config[line.split('=')[0]] = line.split('=')[1]
        else:
            config[line.split('=')[0]] = ''
    return config

class StattrServer(object):
    """A server for the web application Stattr, which tracks competitive activities."""

    def __init__(self):
        self.config = readConf()
        self.sessions = {}
        self.database = psycopg2.connect(database=self.config['dbname'], host=self.config['hostname'], port=int(self.config['port']), user=self.config['dbuser'], password=self.config['dbpass'])
        self.tplList = ['home', 'header', 'login', 'userbar', 'admin', 'eventcreate', 'eventhome', 'usercreate', 'resultsadd']
        self.buildDatabase()
        self.srvFuncGet = {'/tpls.json': self.getTpls, '/conf.json': self.getConf, '/events.json': self.getEvents, '/isadmin.json': self.getAdmin, '/event.json': self.getEvent}
        self.srvFuncPost = {'/login.json': self.login, '/event.json': self.addEvent, '/user.json': self.addUser, '/results.json': self.addResults}
        server_address = ('', 54321)
        stattr_server = wsgi.WSGIServer(server_address, self.requestHandler)
        stattr_server.serve_forever()

    def buildDatabase(self):
        cur = self.database.cursor()
        cur.execute("SELECT * FROM pg_tables WHERE tablename='stattrconf'") # Create config table, if it doesn't exist
        if not cur.fetchone():
            cur.execute("CREATE TABLE stattrconf (sitename varchar, logo varchar, location varchar)")
            
        cur.execute("SELECT * FROM stattrconf")
        if self.config['sitename'] and self.config['logourl'] and self.config['location'] and not cur.fetchone(): # Put in config data, if it's not already there
            cur.execute("INSERT INTO stattrconf VALUES ('%s', '%s', '%s')" % (self.config['sitename'], self.config['logourl'], self.config['location']))
        
        cur.execute("SELECT * FROM pg_tables WHERE tablename='stattrtbls'") # Create table to list activities, if it doesn't exist
        if not cur.fetchone():
            self.database.cursor().execute("CREATE TABLE stattrtbls (id varchar, activity varchar, descr text, official varchar[], fields varchar[], types varchar[])")

        cur.execute("SELECT * FROM pg_tables WHERE tablename='stattrusers'") # Create users table if not there
        if not cur.fetchone():
            cur.execute("CREATE TABLE stattrusers (username varchar, password varchar, admin int, fullname varchar, profile text)")
            cur.execute("INSERT INTO stattrusers VALUES ('%s', '%s', 1, 'admin', '')" % (self.config['adminuser'], hashlib.sha1(self.config['adminpass']).hexdigest()))
        self.database.commit()

    def makeSession(self, username, hostname, admin):
        idNum = hashlib.sha1(username + hostname + str(random.random())).hexdigest()
        self.sessions[idNum] = (username, hostname, admin)
        return idNum

    def requestHandler(self, env, respobj):
        path = env['PATH_INFO']
        query = getData(env['QUERY_STRING'])
        if path == '/':
            return sendFile('text/html', 'index.html', respobj)
        elif path in ['/stattr.js', '/jquery.js', '/crypto.js', '/sha1.js']:
            return sendFile('text/javascript', path[1:], respobj)
        elif path == '/style.css':
            return sendFile('text/css', 'style.css', respobj)
        elif path in self.srvFuncGet and ('_method' not in query or query['_method'][0] == 'GET'):
            return self.srvFuncGet[path](env, respobj)
        elif path in self.srvFuncPost and ('_method' in query and query['_method'][0] == 'POST'):
            return self.srvFuncPost[path](env, respobj)
        else:
            return sendError(query, 'resource does not exist', respobj)

    # -------------------------------
    #     Start server functions
    # -------------------------------
    
    # -------------------
    # GET functions
    # -------------------

    def getTpls(self, env, start_response):
        query_string = getData(env['QUERY_STRING'])
        if 'which' not in query_string:
            return sendError(query_string, 'need to specify which template', start_response)
        elif query_string['which'] not in self.tplList and query_string['which'][0] != 'all':
            return sendError(query_string, 'template does not exist', start_response)
        else:
            ourpath = determine_path()
            which = query_string['which']
            tpls = {}
            if which[0] == 'all':
                which = self.tplList
            for tpl in which:
                openStr = ourpath + '/tpls/' + tpl + '.tpl'
                tpls[tpl] = open(openStr, 'r').read()
        jsonRespond(start_response)
        return mkResponse(query_string, tpls)

    def getConf(self, env, start_response):
        query_string = getData(env['QUERY_STRING'])
        cur = self.database.cursor()
        cur.execute("SELECT * FROM stattrconf")
        sitename, logo, location = cur.fetchone()
        jsonRespond(start_response)
        return mkResponse(query_string, {'sitename': sitename, 'logo': logo, 'location': location})

    def getEvents(self, env, start_response):
        query_string = getData(env['QUERY_STRING'])
        cur = self.database.cursor()
        cur.execute("SELECT * FROM stattrtbls")
        raw_results = cur.fetchall()
        eventslist = []
        for r in raw_results:
            event = {'id': r[0], 'activity': r[1], 'descr': r[2]}
            eventslist.append(event)
        jsonRespond(start_response)
        return mkResponse(query_string, {'events': eventslist})

    def getAdmin(self, env, start_response):
        query_string = getData(env['QUERY_STRING'])
        if not query_string or not 'session' in query_string or not 'username' in query_string:
            return sendError(query_string, 'need a session id and username to complete this request', start_response)
        elif not query_string['session'][0] in self.sessions:
            return sendError(query_string, 'session does not exist', start_response)
        elif self.sessions[query_string['session'][0]][0] != query_string['username'][0]:
            return sendError(query_string, 'username does not match our session id', start_response)
        elif self.sessions[query_string['session'][0]][1] != env['REMOTE_ADDR']:
            return sendError(query_string, 'you are requesting from a different address, please login again', start_response)
        else:
            jsonRespond(start_response)
            return mkResponse(query_string, {'isAdmin': self.sessions[query_string['session'][0]][2]})

    def getEvent(self, env, start_response):
        query_string = getData(env['QUERY_STRING'])
        cur = self.database.cursor(cursor_factory=psycopg2.extras.DictCursor)
        cur.execute("SELECT * FROM pg_tables WHERE tablename='%s'" % query_string['id'][0])
        if not query_string or not 'id' in query_string:
            return sendError(query_string, 'need to know what event you want to complete this request', start_response)
        elif not cur.fetchone():
            return sendError(query_string, 'that event does not seem to exist....', start_response)
        else:
            jsonRespond(start_response)
            cur.execute("SELECT * FROM stattrtbls WHERE id='%s'" % query_string['id'][0])
            tblinfo = list(cur.fetchone())
            cur.execute("SELECT * FROM %s" % query_string['id'][0])
            eventsinfo = cur.fetchall()
            results = []
            page = []
            if len(eventsinfo) > 0:
                for happening in eventsinfo:
                    if len(page) > 9:
                        results.insert(0, page)
                        page = []
                    thisresult = []
                    for field in happening:
                        thisresult.append(field)
                    page.insert(0, thisresult);
                results.insert(0, page)

            if query_string and 'page' in query_string:
                resultObj = {'events': results[query_string['page']]}
            elif len(eventsinfo) <= 0:
                resultObj = {'table': tblinfo}
            else:
                resultObj = {'table': tblinfo, 'events': results[0], 'pages': len(results)}

            return mkResponse(query_string, resultObj)

    # -----------------
    # POST functions
    # -----------------

    def login(self, env, start_response): 
        query_string = getData(env['QUERY_STRING'])
        cur = self.database.cursor()
        if not query_string['username'] or not query_string['password']:
            return sendError(query_string, 'need to supply username and password', start_response)
        cur.execute("SELECT * FROM stattrusers WHERE username='%s'" % query_string['username'][0])
        dbuser = cur.fetchone()
        if not dbuser:
            return sendError(query_string, 'user does not exist', start_response)
        if not query_string['password'][0] == dbuser[1]:
            return sendError(query_string, 'incorrect password', start_response)
        jsonRespond(start_response)
        return mkResponse(query_string, {'session': self.makeSession(query_string['username'][0], env['REMOTE_ADDR'], dbuser[2]), 'username': query_string['username'][0], 'admin': dbuser[2]})

    def addEvent(self, env, start_response):
        query_string = getData(env['QUERY_STRING'])
        if not query_string['activity'] or not query_string['variables'] or not query_string['types'] or not query_string['officials'] or not query_string['descr']:
            jsonRespond(start_response)
            return mkResponse(query_string, {'error': 'data not sufficient to create event'})
        else:
            if not 'id' in query_string:
                query_string['id'] = [query_string['activity'][0].split()[0] + hashlib.sha1(query_string['descr'][0]).hexdigest()[0:8]]
            cur = self.database.cursor()
            cur.execute("SELECT * FROM pg_tables WHERE tablename='%s'" % query_string['id'][0])
            if not cur.fetchone(): # create table, if it doesn't exist
                varstring = "participants varchar[], "
                typestring = "'{\"varchar\", "
                fieldstring = "'{\"participants\", "
                for pair in zip(query_string['variables'], query_string['types']):
                    varstring += '%s %s[], ' % pair
                    typestring += '"%s", ' % pair[1]
                    fieldstring += '"%s", ' % pair[0]
                typestring = typestring[:-2] + "}'"
                fieldstring = fieldstring[:-2] + "}'"
                cur.execute("CREATE TABLE %s (%s)" % (query_string['id'][0], varstring[:-2]))
                officials = "'{"
                query_string['officials'] = query_string['officials'][0].split(',')
                for official in query_string['officials']:
                    while official[0] == ' ':
                        official = official[1:]
                    while official[-1] == ' ':
                        official = official[:-1]
                for official in query_string['officials']:
                    officials += '"%s", ' % official
                officials = officials[:-2] + "}'"
                cur.execute("INSERT INTO stattrtbls VALUES ('%s', '%s', '%s', %s, %s, %s)" % (query_string['id'][0], query_string['activity'][0], query_string['descr'][0], officials, fieldstring, typestring))
                cur.execute("SELECT * FROM stattrtbls")
                self.database.commit()
                cur.close()
                jsonRespond(start_response)
                return mkResponse(query_string, {})
            else:
                jsonRespond(start_response)
                return mkResponse(query_string, {'error': 'event already exists, create a unique ID'})

    def addUser(self, env, start_response):
        query_string = getData(env['QUERY_STRING'])
        if not 'username' in query_string or not 'password' in query_string or not 'fullname' in query_string or not 'admin' in query_string:
            response = {'error': 'data not sufficient to create user'}
        elif self.database.query("SELECT * FROM stattrusers WHERE username='%s'" % query_string['username'][0]).getresult():
            response = {'error': 'user already exists'}
        else:
            self.database.query("INSERT INTO stattrusers VALUES ('%s', '%s', '%s', '%s', '%s')" % (query_string['username'][0], query_string['password'][0], query_string['admin'][0], query_string['fullname'][0], query_string['profile'][0]))
            response = {}
        jsonRespond(start_response)
        return mkResponse(query_string, response)

    def addResults(self, env, start_response):
        query_string = getData(env['QUERY_STRING'])
        if not 'username' in query_string or not 'session' in query_string:
            response = {'error': 'need to be logged in to add results'}
        elif not query_string['session'][0] in self.sessions or self.sessions[query_string['session'][0]][0] != query_string['username'][0] or self.sessions[query_string['session'][0]][1] != env['REMOTE_ADDR']:
            response = {'error': 'session data does not match query string'}
        elif not 'event' in query_string or not 'results' in query_string:
            response = {'error': 'server needs data to create a resultset'}
        else:
            cur = self.database.cursor()
            results = [field.split(',') for field in query_string['results']]
            cur.execute("SELECT * FROM stattrtbls WHERE id='%s'" % query_string['event'][0])
            fields = list(cur.fetchone())[5]
            while 'bool' in fields:
                ouri = fields.index('bool')
                fields[ouri] = 'done'
                results[ouri] = [thisbool == 'true' for thisbool in results[ouri]]
            while 'int' in fields or 'double' in fields:
                ouri = fields.index('int') or fields.index('double')
                thistype = fields[ouri]
                fields[ouri] = 'done'
                results[ouri] = [(int(thisnum) if thistype == 'int' else float(thisnum)) for thisnum in results[ouri]]
            execstr = 'INSERT INTO ' + query_string['event'][0] + ' VALUES (' + ', '.join(['%s' for i in query_string['results']]) + ')'
            cur.execute(execstr, results)
            response = {}
            self.database.commit();

        jsonRespond(start_response)
        return mkResponse(query_string, response)

def start():
    ourserver = StattrServer()
